/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.worker.controller;

import static org.ourgrid.common.CommonConstants.PUT_TRANSFER;
import static org.ourgrid.common.CommonConstants.STORE_TRANSFER;

import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.ourgrid.broker.actions.ErrorOcurredMessageHandle;
import org.ourgrid.common.FileTransferInfo;
import org.ourgrid.common.interfaces.Worker;
import org.ourgrid.common.interfaces.WorkerClient;
import org.ourgrid.common.interfaces.to.GridProcessErrorTypes;
import org.ourgrid.reqtrace.Req;
import org.ourgrid.worker.WorkerConstants;
import org.ourgrid.worker.dao.FileTransferDAO;
import org.ourgrid.worker.dao.WorkerStatusDAO;
import org.ourgrid.worker.messages.FileTransferControllerMessages;

import br.edu.ufcg.lsd.commune.container.servicemanager.ServiceManager;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;
import br.edu.ufcg.lsd.commune.processor.filetransfer.IncomingTransferHandle;
import br.edu.ufcg.lsd.commune.processor.filetransfer.OutgoingTransferHandle;

public class FileTransferController {

	private static FileTransferController instance = null;
	
	@Req("REQ080")
	public static synchronized FileTransferController getInstance() {
		if (instance == null) {
			instance = new FileTransferController();
		}
		return instance;
	}
	
	@Req("REQ080")
	public void cancelCurrentTransfers(ServiceManager serviceManager) {
		
		FileTransferDAO fileTransferDAO = serviceManager.getDAO(FileTransferDAO.class);
		
		for (IncomingTransferHandle handle : fileTransferDAO.getIncomingFileHandles()) {
			serviceManager.cancelIncomingTransfer(handle);
			fileTransferDAO.removeIncomingFile(handle);
		}
		
		for (OutgoingTransferHandle handle : fileTransferDAO.getUploadingFileHandles()) {
			serviceManager.cancelOutgoingTransfer(handle);
			fileTransferDAO.removeUploadingFile(handle);
		}
	}

	@Req("REQ080")
	public void rejectTransferRequest(IncomingTransferHandle handle, ServiceManager serviceManager) {
		serviceManager.rejectTransfer(handle);
	}

	@Req("REQ081")
	private void rejectInvalidRequest(IncomingTransferHandle handle, ServiceManager serviceManager) {
		serviceManager.rejectTransfer(handle);
		cancelCurrentTransfers(serviceManager);
		serviceManager.getDAO(WorkerStatusDAO.class).setErrorState(true);
	}
	
	@Req("REQ080")
	public void acceptTransferRequest(IncomingTransferHandle handle, ServiceManager serviceManager) {
		
		String path = handle.getDestinationFileName();
		String transferDescription = handle.getDescription();
		
		WorkerStatusDAO workerStatusDAO = serviceManager.getDAO(WorkerStatusDAO.class);
		FileTransferDAO fileTransferDAO = serviceManager.getDAO(FileTransferDAO.class);
		String consumerPublicKey = workerStatusDAO.getConsumerPublicKey();
		
		if (!validateTransferDescription(transferDescription)) {
			serviceManager.getLog().warn(FileTransferControllerMessages.
					getClientRequestsToTransferFileWithInvalidTransferDescription(transferDescription, consumerPublicKey));
			rejectInvalidRequest(handle, serviceManager);
			return;
		}
		
		if (fileTransferDAO.containsHandle(handle)) {
			serviceManager.getLog().error(FileTransferControllerMessages.
					getClientRequestsToTransferFileWithRepeatedHandleMessage(handle.getId(), consumerPublicKey));
			errorOcurred(GridProcessErrorTypes.INVALID_SESSION, serviceManager);
			cancelCurrentTransfers(serviceManager);
			return;
		}
		
		String solvedDir;
		
		try {
			if (transferDescription.equals(PUT_TRANSFER)) {
				solvedDir = EnvironmentController.getInstance().solvePlaypenDir(path, serviceManager);
			} else {
				solvedDir = EnvironmentController.getInstance().solveStorageDir(path, serviceManager);
			}
		} catch (IOException e) {
			serviceManager.getLog().warn(FileTransferControllerMessages.
					getClientRequestsToTranferFileButErrorOccuredOnSolvingPathMessage(path, handle.getFileSize(),
							handle.getId(), consumerPublicKey, e.getMessage()));
			rejectInvalidRequest(handle, serviceManager);
			return;
		}
		
		if (fileTransferDAO.containsIncomingFile(solvedDir)) {
			serviceManager.getLog().warn(FileTransferControllerMessages.
					getClientRequestsToTranferIncomingFileMessage(solvedDir, consumerPublicKey));
			rejectInvalidRequest(handle, serviceManager);
			return;
		}
		
		serviceManager.getLog().debug(FileTransferControllerMessages.
				getTransferRequestAcceptedMessage(solvedDir, handle.getId(), consumerPublicKey));
		
		File file = new File(solvedDir);
		handle.setFile(file);
		fileTransferDAO.addIncomingFile(handle, solvedDir);
		
		Worker worker = (Worker) serviceManager.getObjectDeployment(WorkerConstants.WORKER).getObject();
		
		serviceManager.acceptTransfer(handle, worker);
	}
	
	private boolean validateTransferDescription(String transferDescription) {
		return (PUT_TRANSFER.equals(transferDescription) || STORE_TRANSFER.equals(transferDescription));
	}

	@Req("REQ080")
	public void incomingTransferFailed(IncomingTransferHandle handle, Exception failCause, long amountWritten,
			ServiceManager serviceManager) {
		
		String incomingFileFailedPath =  serviceManager.getDAO(FileTransferDAO.class).removeIncomingFile(handle);
		String consumerPublicKey = serviceManager.getDAO(WorkerStatusDAO.class).getConsumerPublicKey();
		
		serviceManager.getLog().error(FileTransferControllerMessages.
				getIncomingTransferFailedMessage(incomingFileFailedPath, amountWritten, consumerPublicKey));
		errorOcurred(failCause, GridProcessErrorTypes.FILE_TRANSFER_ERROR, serviceManager);
	}
	
	@Req("REQ080")
	public void incomingTransferCompleted(IncomingTransferHandle handle, long amountWritten, ServiceManager serviceManager) {
		
		
		serviceManager.getDAO(FileTransferDAO.class).removeIncomingFile(handle);
		String consumerPublicKey = serviceManager.getDAO(WorkerStatusDAO.class).getConsumerPublicKey();
		
		serviceManager.getLog().debug(FileTransferControllerMessages.
				getIncomingTransferCompletedMessage(handle.getId(), amountWritten, consumerPublicKey));
	}
	
	@Req("REQ081")
	private void errorOcurred(Exception exception, GridProcessErrorTypes error, ServiceManager serviceManager) {
		WorkerStatusDAO workerStatusDAO = serviceManager.getDAO(WorkerStatusDAO.class);
		workerStatusDAO.getConsumer().sendMessage(new ErrorOcurredMessageHandle(new GridProcessError(exception, error)));
		workerStatusDAO.setErrorState(true);
	}
	
	@Req("REQ081")
	private void errorOcurred(GridProcessErrorTypes error, ServiceManager serviceManager) {
		errorOcurred(null, error, serviceManager);
	}
	
	@Req("REQ081")
	public void fileRejected(OutgoingTransferHandle handle, ServiceManager serviceManager) {
		
		String consumerPublicKey = serviceManager.getDAO(WorkerStatusDAO.class).getConsumerPublicKey();
		String rejectedFilePath = serviceManager.getDAO(FileTransferDAO.class).getUploadingFile(handle).getAbsolutePath();
		
		serviceManager.getLog().error(FileTransferControllerMessages.getWorkerReceivesAFileTransferRejectedMessage(
						rejectedFilePath, handle.getId(), consumerPublicKey));
		errorOcurred(GridProcessErrorTypes.APPLICATION_ERROR, serviceManager);
	}
	
	@Req("REQ081")
	public void outgoingTransferFailed(OutgoingTransferHandle handle, Exception exception,
			long amountUploaded, ServiceManager serviceManager) {
		
		String consumerPublicKey = serviceManager.getDAO(WorkerStatusDAO.class).getConsumerPublicKey();
		String failedFilePath = serviceManager.getDAO(FileTransferDAO.class).getUploadingFile(handle).getAbsolutePath();
		
		serviceManager.getLog().error(FileTransferControllerMessages.
				getWorkerReceivesAnOutgoingFileTransferFailedMessage(
						failedFilePath, handle.getId(), amountUploaded, consumerPublicKey));
		errorOcurred(GridProcessErrorTypes.APPLICATION_ERROR, serviceManager);
	}
	
	@Req("REQ081")
	public void outgoingTransferCancelled(OutgoingTransferHandle handle, long amountUploaded, ServiceManager serviceManager) {
	
		String consumerPublicKey = serviceManager.getDAO(WorkerStatusDAO.class).getConsumerPublicKey();
		String cancelledFilePath = serviceManager.getDAO(FileTransferDAO.class).getUploadingFile(handle).getAbsolutePath();
		
		serviceManager.getLog().error(FileTransferControllerMessages.
				getWorkerReceivesAnOutgoingFileTransferCancelledMessage(
						cancelledFilePath, handle.getId(), amountUploaded, consumerPublicKey));
		errorOcurred(GridProcessErrorTypes.APPLICATION_ERROR, serviceManager);
	}

	@Req("REQ081")
	public void outgoingTransferCompleted(OutgoingTransferHandle handle, long amountUploaded, ServiceManager serviceManager) {
		
		FileTransferDAO fileTransferDAO = serviceManager.getDAO(FileTransferDAO.class);
		
		String consumerPublicKey = serviceManager.getDAO(WorkerStatusDAO.class).getConsumerPublicKey();
		String filePath = fileTransferDAO.removeUploadingFile(handle);
		
		serviceManager.getLog().debug(FileTransferControllerMessages.
				getWorkerReceivesAnOutgoingFileTransferCompletedMessage(filePath, handle.getId(), amountUploaded, consumerPublicKey));
		
		if(!fileTransferDAO.hasUploadingFile()) {
			serviceManager.getLog().info(FileTransferControllerMessages.getAllUploadsFinishMessage());
		}
	}
	
	@Req("REQ081")
	public void startTransfer(DeploymentID destinationID, ServiceManager serviceManager, FileTransferInfo... files) {
		
		List<FileTransferInfo> solvedInfos = solveFilesInfo(serviceManager, files);
		
		FileTransferDAO fileTransferDAO = serviceManager.getDAO(FileTransferDAO.class);
		
		if(solvedInfos != null) {
			for (FileTransferInfo info : solvedInfos) {
				
				File file = new File(info.getFilePath());
				String filePath = file.getAbsolutePath();
				
				if (fileTransferDAO.containsUploadingFile(filePath)) {
					serviceManager.getLog().warn(FileTransferControllerMessages.
							getClientRequestsToRecoverAnUploadingFileMessage(filePath, destinationID.getPublicKey()));
					return;
				}
				
				serviceManager.getLog().debug(FileTransferControllerMessages.getWorkerStartsFileTransferWithSuccess(filePath,
						info.getTransferHandleID(), destinationID.getPublicKey()));
				
				Worker worker = (Worker) serviceManager.getObjectDeployment(
						WorkerConstants.WORKER).getObject();
				OutgoingTransferHandle outgoingTransferHandle = new OutgoingTransferHandle(
						info.getTransferHandleID(), filePath, null, null, destinationID);
				
				fileTransferDAO.addUploadingFile(outgoingTransferHandle, filePath);
				serviceManager.startTransfer(outgoingTransferHandle, worker);
			}
		}
	}
	
	@Req("REQ081")
	private List<FileTransferInfo> solveFilesInfo(ServiceManager serviceManager, FileTransferInfo... files) {
		
		WorkerClient consumer = serviceManager.getDAO(WorkerStatusDAO.class).getConsumer();
		String consumerPublicKey = serviceManager.getStubDeploymentID(consumer).getPublicKey();
		
		if(files == null) {
			serviceManager.getLog().warn(FileTransferControllerMessages.
					getClientRequestsToRecoverFilesWithNullFilesMessage(consumerPublicKey));
			errorOcurred(GridProcessErrorTypes.APPLICATION_ERROR, serviceManager);
			return null;
		}
		
		List<FileTransferInfo> solvedInfoList = new LinkedList<FileTransferInfo>();
		
		for (FileTransferInfo info : files) {
			
			String filePath = info.getFilePath();
			
			try {
				String solvedDir = EnvironmentController.getInstance().solveDir(filePath, serviceManager);

				if (hasInfoWithSamePath(solvedInfoList, solvedDir)) {
					serviceManager.getLog().warn(FileTransferControllerMessages.
							getClientRequestsToRecoverFilesButThereAreFilesWithSamePathMessage(filePath, consumerPublicKey));
					errorOcurred(GridProcessErrorTypes.APPLICATION_ERROR, serviceManager);
					return null;
				}
				
				File file = new File(solvedDir);
				if (!file.exists()) {
					serviceManager.getLog().warn(FileTransferControllerMessages.
							getClientRequestsToRecoverAnInexistentFileMessage(solvedDir, consumerPublicKey));
					errorOcurred(GridProcessErrorTypes.APPLICATION_ERROR, serviceManager);
					return null;
				}
				
				solvedInfoList.add(new FileTransferInfo(info.getTransferHandleID(), solvedDir));
			} catch (IOException e) {
				serviceManager.getLog().warn(FileTransferControllerMessages.
						getClientRequestsToRecoverFilesButErrorOccuredOnSolvingPathMessage(filePath, consumerPublicKey, e.getMessage()));
				errorOcurred(e, GridProcessErrorTypes.IO_ERROR, serviceManager);
				return null;
			}
		}
		
		return solvedInfoList;
	}
	
	@Req("REQ081")
	private boolean hasInfoWithSamePath(List<FileTransferInfo> solvedInfoList, String filePath) {
		for (FileTransferInfo info : solvedInfoList) {
			if (info.getFilePath().equals(filePath)) {
				return true;
			}
		}
		return false;
	}

}